package com.org.platform.support.conponents.request.repeat;

import com.org.platform.enums.HttpStatusEnum;
import com.org.platform.model.ResultModel;
import com.org.platform.support.conponents.request.RequestBaseParam;
import com.org.platform.support.conponents.request.RequestKeyType;
import com.org.platform.utils.CommonUtil;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

/**
 * @author duheng
 * @Date 2021/11/17 8:53
 */
@Slf4j
@Order(Integer.MIN_VALUE)
@Aspect
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true)
@Component
public class RequestRepeatAop {

	@Autowired
	private StringRedisTemplate stringRedisTemplate;

	@Around(value = "execution(* com.org..*Controller.*(..))&&(@annotation(com.org.platform.support.conponents.request.repeat.RequestRepeatCheck))")
	public Object requestCache(ProceedingJoinPoint joinPoint) throws Throwable {
		Class clazz = joinPoint.getTarget().getClass();
		String methodName = joinPoint.getSignature().getName();
		Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
		Object[] args = joinPoint.getArgs(); // 参数值
		String[] argNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames(); // 参数名
		Method method = clazz.getMethod(methodName, parameterTypes);
		RequestRepeatCheck requestRepeatCheck = method.getAnnotation(RequestRepeatCheck.class);
		if (null == requestRepeatCheck) {
			return joinPoint.proceed(); //执行目标方法
		}
		String keyPrefix = requestRepeatCheck.keyPrefix();
		String requestKeyType = requestRepeatCheck.requestKeyType();
		String requestKeyName = requestRepeatCheck.requestKeyName();
		int timeoutSeconds = requestRepeatCheck.timeoutSeconds();
		if (CommonUtil.equals(requestKeyType, RequestKeyType.Param.getName())) {
			ResultModel checkRequestParam = checkRequestParam(argNames, args, keyPrefix, requestKeyName, timeoutSeconds);
			if (!checkRequestParam.isSuccess()) {
				return checkRequestParam;
			}
		}
		if (CommonUtil.equals(requestKeyType, RequestKeyType.Body.getName())) {
			ResultModel checkRequestBody = checkRequestBody(args, keyPrefix, requestKeyName, timeoutSeconds);
			if (!checkRequestBody.isSuccess()) {
				return checkRequestBody;
			}
		}
		if (CommonUtil.equals(requestKeyType, RequestKeyType.Request.getName())) {
			ResultModel checkRequestReq = checkRequestReq(args, keyPrefix, requestKeyName, timeoutSeconds);
			if (!checkRequestReq.isSuccess()) {
				return checkRequestReq;
			}
		}
		Object object = joinPoint.proceed(); //执行目标方法
		return object;
	}

	private ResultModel checkRequestParam(String[] argNames, Object[] args, String keyPrefix, String requestKeyName, int timeoutSeconds) {
		if (null == argNames || argNames.length <= 0) {
			return ResultModel.newSuccess(); //执行目标方法
		}
		for (int i = 0; i < argNames.length; i++) {
			String argName = argNames[i];
			Object arg = args[i];
			if (CommonUtil.isNullOrEmpty(argName) || CommonUtil.isNullOrEmpty(arg)) {
				continue;
			}
			if (!CommonUtil.equals(argName, requestKeyName)) {
				continue;
			}
			String cacheKey = keyPrefix + arg.toString();
			boolean bool = stringRedisTemplate.opsForValue().setIfAbsent(cacheKey, "1", timeoutSeconds, TimeUnit.SECONDS);
			if (!bool) {
				log.info("cachekey:{} repeat", cacheKey);
				return ResultModel.newFail(HttpStatusEnum.ERROR429.getCode(), HttpStatusEnum.ERROR429.getMessage());
			}
		}
		return ResultModel.newSuccess();
	}

	private ResultModel checkRequestBody(Object[] args, String keyPrefix, String requestKeyName, int timeoutSeconds) {
		if (null == args || args.length <= 0) {
			return ResultModel.newSuccess();
		}
		for (int i = 0; i < args.length; i++) {
			Object arge = args[i];
			if (null == arge) {
				continue;
			}
			String cacheKey = null;
			boolean cache = false;
			if (arge instanceof Map) {
				Map result = (Map) arge;
				if (null == result || result.isEmpty() || !result.containsKey(requestKeyName)) {
					continue;
				}
				Object val = result.get(requestKeyName);
				cacheKey = keyPrefix + (val == null ? "" : val.toString());
				cache = true;
			} else if (arge instanceof RequestBaseParam) {
				try {
					Field[] fields = arge.getClass().getDeclaredFields();
					if (null == fields || fields.length <= 0) {
						continue;
					}
					for (Field field : fields) {
						field.setAccessible(true);
						String fieldName = field.getName();
						if (!CommonUtil.equals(requestKeyName, fieldName)) {
							continue;
						}
						char[] ch = fieldName.toCharArray();
						if (ch[0] >= 'a' && ch[0] <= 'z') {
							ch[0] = (char) (ch[0] - 32);
						}
						String theMethodName = "get" + new String(ch);
						Method method = arge.getClass().getDeclaredMethod(theMethodName, new Class[0]);
						Object val = method.invoke(arge, new Object[0]);
						cacheKey = keyPrefix + (val == null ? "" : val.toString());
						cache = true;
					}
				} catch (Exception ex) {
					log.error("get RequestBaseParam value error", ex);
				}
			}
			if (!cache) {
				continue;
			}
			boolean bool = stringRedisTemplate.opsForValue().setIfAbsent(cacheKey, "1", timeoutSeconds, TimeUnit.SECONDS);
			if (!bool) {
				log.info("cachekey:{} repeat", cacheKey);
				return ResultModel.newFail(HttpStatusEnum.ERROR429.getCode(), HttpStatusEnum.ERROR429.getMessage());
			}
		}
		return ResultModel.newSuccess();
	}

	private ResultModel checkRequestReq(Object[] args, String keyPrefix, String requestKeyName, int timeoutSeconds) {
		if (null == args || args.length <= 0) {
			return ResultModel.newSuccess();
		}
		for (int i = 0; i < args.length; i++) {
			Object arg = args[i];
			if (null == arg || !(arg instanceof HttpServletRequest)) {
				continue;
			}
			HttpServletRequest requestObj = (HttpServletRequest) arg;
			if (null == requestObj) {
				continue;
			}
			Map<String, String[]> parameterMap = requestObj.getParameterMap();
			if (null == parameterMap || parameterMap.isEmpty()) {
				continue;
			}
			if (!parameterMap.containsKey(requestKeyName)) {
				continue;
			}
			String[] requestValue = parameterMap.get(requestKeyName);
			if (null == requestValue && requestValue.length <= 0) {
				continue;
			}
			String cacheKey = keyPrefix + requestValue[0];
			boolean bool = stringRedisTemplate.opsForValue().setIfAbsent(cacheKey, "1", timeoutSeconds, TimeUnit.SECONDS);
			if (!bool) {
				log.info("cachekey:{} repeat", cacheKey);
				return ResultModel.newFail(HttpStatusEnum.ERROR429.getCode(), HttpStatusEnum.ERROR429.getMessage());
			}
		}
		return ResultModel.newSuccess();
	}
}
